﻿#region Header
// ------------------------ Licence / Copyright ------------------------
// 
// ScrumTable for Scrum Meeting Support
// Copyright © HSR - Hochschule für Technik Rapperswil
// All Rights Reserved
// 
// Author:
//  Michael Gfeller Silvan Gehrig Patrick Boos
// 
// ---------------------------------------------------------------------
#endregion

#region Usings

using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Xml.Serialization;

using ScrumTable.Common.Collections;

#endregion

namespace ScrumTable.Common.Config.Xml
{
    /// <summary>
    /// Represents a default implementation of driver configuration node.
    /// </summary>
    public abstract class XmlConfigSectionBase : IXmlConfigSection
    {
        #region Declarations
        //--------------------------------------------------------------------
        // Declarations
        //--------------------------------------------------------------------

        private const string NameAttribute = "Name";

        private bool _dataLoaded = false;
        private IDictionary<string, IXmlConfigProperty> _properties;
        private IDictionary<string, ICollection<IXmlConfigSection>> _sections;
        private string _sectionName;

        #endregion

        #region Properties
        //--------------------------------------------------------------------
        // Properties
        //--------------------------------------------------------------------

        /// <summary>
        /// Gets the name of the current configuration section.
        /// </summary>
        public virtual string SectionName
        {
            get
            {
                EnsureDataLoaded();
                return _sectionName;
            }
        }

        /// <summary>
        /// Gets the child configuration sections for the current instance.
        /// </summary>
        /// <remarks>
        /// The current list is READ ONLY.
        /// </remarks>
        public IDictionary<string, ICollection<IXmlConfigSection>> Sections
        {
            get
            {
                EnsureDataLoaded();
                return _sections;
            }
        }

        /// <summary>
        /// Gets the configuration properties of the current section.
        /// Caution:
        /// The property named "Name" is an identifier and will
        /// not be listed in the Properties dictionary!
        /// </summary>
        /// <remarks>
        /// The current list is READ ONLY.
        /// </remarks>
        public IDictionary<string, IXmlConfigProperty> Properties
        {
            get
            {
                EnsureDataLoaded();
                return _properties;
            }
        }

        #endregion

        #region Constructors / Destructor

        //--------------------------------------------------------------------
        // Constructors / Destructor
        //--------------------------------------------------------------------

        /// <summary>
        /// Initializes a new instance of the <see cref="XmlConfigSectionBase"/> class.
        /// </summary>
        protected XmlConfigSectionBase()
        {
            _sectionName = GetType().Name;
        }

        #endregion

        #region Methods
        //--------------------------------------------------------------------
        // Methods
        //--------------------------------------------------------------------

        internal void EnsureDataLoaded()
        {
            if (!_dataLoaded)
            {
                _dataLoaded = true; // avoid endless recursion

                IDictionary<string, IXmlConfigProperty> properties = new Dictionary<string, IXmlConfigProperty>();
                IDictionary<string, ICollection<IXmlConfigSection>> sections = new Dictionary<string, ICollection<IXmlConfigSection>>();

                InitializeType(properties, sections);

                _properties = new ReadOnlyDictionary<string, IXmlConfigProperty>(properties);
                _sections = new ReadOnlyDictionary<string, ICollection<IXmlConfigSection>>(sections);

                if (_properties.ContainsKey(NameAttribute))
                {
                    _sectionName = _properties[NameAttribute].Value as string;
                }

                // ensure that data loaded in child sections
                (from sectionPairs in _sections
                 from section in sectionPairs.Value
                 where (section is XmlConfigSectionBase)
                 select (section as XmlConfigSectionBase)).ForEach(s => s.EnsureDataLoaded());
            }
        }

        private void InitializeType(
            IDictionary<string, IXmlConfigProperty> properties,
            IDictionary<string, ICollection<IXmlConfigSection>> sections)
        {
            foreach (var driverConfigProperty in GetType().GetProperties(BindingFlags.Public | BindingFlags.GetField | BindingFlags.Instance))
            {
                if (driverConfigProperty.GetGetMethod() != null
                    && driverConfigProperty.GetSetMethod() != null
                    && driverConfigProperty.GetCustomAttributes(typeof(XmlIgnoreAttribute), true).Length == 0)
                {
                    InitializePropertyInfo(driverConfigProperty, properties, sections);
                }
            }
        }

        private void InitializePropertyInfo(
            PropertyInfo driverConfigProperty,
            IDictionary<string, IXmlConfigProperty> properties,
            IDictionary<string, ICollection<IXmlConfigSection>> sections)
        {
            if (IsSimpleDriverConfigSection(driverConfigProperty))
            {
                sections[driverConfigProperty.Name] = new ReadOnlyCollection<IXmlConfigSection>(
                    GetSimpleDriverConfigSection(driverConfigProperty));
            }
            else if (IsComplexDriverConfigSection(driverConfigProperty))
            {
                sections[driverConfigProperty.Name] = new ReadOnlyCollection<IXmlConfigSection>(
                    GetComplexDriverConfigSections(driverConfigProperty));
            }
            else if (IsDriverConfigProperty(driverConfigProperty))
            {
                properties[driverConfigProperty.Name] = new XmlConfigProperty(
                    this,
                    driverConfigProperty,
                    (driverConfigProperty.Name == NameAttribute || HasReadOnlyAttribute(driverConfigProperty)));
            }
        }

        private IEnumerable<IXmlConfigSection> GetSimpleDriverConfigSection(PropertyInfo driverConfigProperty)
        {
            IXmlConfigSection configSection = (driverConfigProperty.GetGetMethod().Invoke(this, null) as IXmlConfigSection);

            if (configSection != null)
            {
                return new Enumerable<IXmlConfigSection>(configSection);
            }
            return new IXmlConfigSection[0];
        }

        private IEnumerable<IXmlConfigSection> GetComplexDriverConfigSections(PropertyInfo driverConfigProperty)
        {
            IEnumerable configSections = (IEnumerable)driverConfigProperty.GetGetMethod().Invoke(this, null);
            
            if (configSections != null)
            {
                IList<IXmlConfigSection> driverConfigSections = new List<IXmlConfigSection>();

                foreach (var configSection in configSections)
                {
                    if (configSection is IXmlConfigSection)
                    {
                        driverConfigSections.Add(configSection as IXmlConfigSection);
                    }
                }
                return driverConfigSections;
            }
            return new IXmlConfigSection[0];
        }

        private bool IsSimpleDriverConfigSection(PropertyInfo driverConfigProperty)
        {
            return typeof (IXmlConfigSection).IsAssignableFrom(driverConfigProperty.GetGetMethod().ReturnType);
        }

        private bool IsComplexDriverConfigSection(PropertyInfo driverConfigProperty)
        {
            return ((driverConfigProperty.GetCustomAttributes(typeof(XmlElementAttribute), true).Length > 0
                || driverConfigProperty.GetCustomAttributes(typeof(XmlArrayAttribute), true).Length > 0)
                &&  typeof(IEnumerable).IsAssignableFrom(driverConfigProperty.GetGetMethod().ReturnType));
        }

        private bool IsDriverConfigProperty(PropertyInfo driverConfigProperty)
        {
            return (driverConfigProperty.GetCustomAttributes(typeof (XmlAttributeAttribute), true).Length > 0);
        }

        private bool HasReadOnlyAttribute(PropertyInfo driverConfigProperty)
        {
            return (driverConfigProperty.GetCustomAttributes(typeof (XmlConfigReadOnlyAttribute), true).Length > 0);
        }

        #endregion

        #region Events
        //--------------------------------------------------------------------
        // Events
        //--------------------------------------------------------------------

        #endregion
    }
}
